#include <stdio.h>
#include <stdlib.h>

#include <config.h>
#include <systems.h>
#include <page.h>
#include <gobjs.h>
#include <linux_list.h>
#include <misc.h>

#ifdef GPU_SUPPORT
#include <drivers/graphic/gpu.h>
#endif

#ifdef INPUT_DEVICE_SUPPORT
#include <drivers/input/input.h>
#endif

extern int xres, yres;

BOOLEAN defer_gobj_by_page = false;
PAGE *current_page = NULL;

PAGE *PGCreate(void)
{
    PAGE *p;

    p = (PAGE *)malloc(sizeof(PAGE));
    memset(p, 0x0, sizeof(PAGE));
    INIT_LIST_HEAD(&p->gobj_link_root);
    p->gobj_counter = 0;
    
    return p;
}

int PGDisplay(PAGE *this_page)
{
    GOBJ_LINK_NODE *gnode;
    struct list_head *node;
    
    if(this_page == NULL)
        return -1;

#ifdef GPU_SUPPORT
    gpu_set_tp_rate(M100V0);
#endif

#ifdef INPUT_DEVICE_SUPPORT
    InputDeviceLock();
#endif

    current_page = this_page;
    defer_gobj_by_page = true;
    
    if(this_page->pg_background) {
        this_page->pg_background->draw((void *)(this_page->pg_background));
    }
    
    list_for_each(node, &this_page->gobj_link_root) {
        gnode = container_of(node, GOBJ_LINK_NODE, list);
        gnode->gobj->draw((void *)(gnode->gobj));
    }
    
    GraphicFlush();
    
    defer_gobj_by_page = false;
    
#ifdef INPUT_DEVICE_SUPPORT
    InputDeviceUnlock();
#endif

    return 0;
}

void PGAddGObj(PAGE *this_page, GOBJ *gobj)
{
    GOBJ_LINK_NODE *gobj_link_node;
    GOBJ_LINK_NODE *gnode;
    OVERLAPPED_GOBJ_LINK_NODE *ovrlap_node;
    struct list_head *node;
    PAGE_LIST *pg_list_node;

    if(this_page == NULL) {
        DPRINTF("Page is not specified\n");
        return;
    }
    
    if(gobj == NULL) {
        DPRINTF("GObj is not initialized\n");
        return;
    }

    /* Create a gobj link node */
    gobj_link_node = (GOBJ_LINK_NODE *)malloc(sizeof(GOBJ_LINK_NODE));

    /* Initialize GOBJ_LINK_NODE */
    gobj_link_node->gobj = gobj;
    INIT_LIST_HEAD(&gobj_link_node->list);
    gobj_link_node->sx = gobj->x;
    gobj_link_node->sy = gobj->y;
    gobj_link_node->dx = gobj->x + gobj->width;
    gobj_link_node->dy = gobj->y + gobj->height;

    /* Insert current gobj to overlapped_gobj_list of each gobj of this_page if overlapped */
    list_for_each(node, &this_page->gobj_link_root) {
        gnode = container_of(node, GOBJ_LINK_NODE, list);
        if(!is_in_region(gobj_link_node->sx, gobj_link_node->sy,
                         gnode->sx, gnode->sy, gnode->dx, gnode->dy))
            if(!is_in_region(gobj_link_node->dx, gobj_link_node->sy,
                             gnode->sx, gnode->sy, gnode->dx, gnode->dy))
                if(!is_in_region(gobj_link_node->sx, gobj_link_node->dy,
                                 gnode->sx, gnode->sy, gnode->dx, gnode->dy))
                    if(!is_in_region(gobj_link_node->dx, gobj_link_node->dy,
                                     gnode->sx, gnode->sy, gnode->dx, gnode->dy))
                        continue;
        
        ovrlap_node = (OVERLAPPED_GOBJ_LINK_NODE *)
                      malloc(sizeof(OVERLAPPED_GOBJ_LINK_NODE));
        INIT_LIST_HEAD(&ovrlap_node->list);
        ovrlap_node->gobj = gobj;
        list_add_tail(&ovrlap_node->list, &gnode->gobj->overlapped_gobj_list);
        
        //gobj_link_node->gobj->prev_overlapped_gobj = gnode->gobj;
    }

    /* Add this gobj_link_node to gobj list in current page */
    list_add_tail(&gobj_link_node->list, &this_page->gobj_link_root);
    ++this_page->gobj_counter;
    gobj_link_node->gobj->pid = this_page->gobj_counter;
    
    /* Add this_page to the pg_list in gobj */
    pg_list_node = (PAGE_LIST *)malloc(sizeof(PAGE_LIST));
    pg_list_node->page = this_page;
    list_add(&pg_list_node->list, &gobj->pg_list);
}


void PGSetBackground(PAGE *this_page, u8 *bg_img, COLOR *bg_color)
{
    int ret;
    GOBJ *gobj;
    
    if(!bg_color)
        bg_color = pCOLOR_BLACK;
        
    gobj = CreateGObj(0, 0, xres, yres, bg_color);
    
    if(bg_img) {
        ret = gobj->setBackground(gobj, bg_img, FALSE);
        if(ret < 0) {
            WARNING("Fault to set background image of page, error = %d", ret);
            DestroyGObj(gobj);
            return;
        }
        if(gobj->background->width != xres || gobj->background->height != yres)
            WARNING("Page background is not fit to the screen\n");
    }
    this_page->pg_background = gobj;
    //gobj->cache_fb_region = false;
}

int PGPushGObjTop(PAGE *this_page, GOBJ *gobj)
{
    struct list_head *_node1, *_node2, *_node_tmp;
    GOBJ_LINK_NODE *link_node;
    BOOLEAN contained_by_page = false;
    PAGE_LIST *pg_lst;
    OVERLAPPED_GOBJ_LINK_NODE *overlapped_node;
    OVERLAPPED_GOBJ_LINK_NODE *overlapped_new_node;
    
    list_for_each(_node1, &gobj->pg_list) {
        pg_lst = container_of(_node1, PAGE_LIST, list);
        if(pg_lst->page == this_page) {
            contained_by_page = true;
            break;
        }
    }
    
    if(!contained_by_page) {
        WARNING("GObj does not belong to the given page\n");
        return -1;
    }
    
    list_for_each(_node1, &this_page->gobj_link_root) {
        link_node = container_of(_node1, GOBJ_LINK_NODE, list);
        if(link_node->gobj == gobj) {
            /* Ignore operation if on top already */
            if(_node1->next == &this_page->gobj_link_root)
                return 0;
            list_move_tail(_node1, &this_page->gobj_link_root);
            /* Update overlapped gobj list */
            list_for_each_safe(_node2, _node_tmp, &gobj->overlapped_gobj_list) {
                overlapped_node = container_of(_node2, OVERLAPPED_GOBJ_LINK_NODE, list);
                overlapped_new_node = (OVERLAPPED_GOBJ_LINK_NODE *)
                                      malloc(sizeof(OVERLAPPED_GOBJ_LINK_NODE));
                INIT_LIST_HEAD(&overlapped_new_node->list);
                overlapped_new_node->gobj = gobj;
                list_add_tail(&overlapped_new_node->list, 
                              &overlapped_node->gobj->overlapped_gobj_list);
                list_del(_node2);
                free(overlapped_node);
            }
            return 0;
        }
    }
    
    return -2; /* GObj is not contained in GObj_link */
}

int PGPushGObjBottom(PAGE *this_page, GOBJ *gobj)
{
    struct list_head *_node1, *_node2, *_node3, *_node_tmp;
    GOBJ_LINK_NODE *link_node, *link_node2;
    BOOLEAN contained_by_page = false;
    PAGE_LIST *pg_lst;
    OVERLAPPED_GOBJ_LINK_NODE *overlapped_node;
    
    list_for_each(_node1, &gobj->pg_list) {
        pg_lst = container_of(_node1, PAGE_LIST, list);
        if(pg_lst->page == this_page) {
            contained_by_page = true;
            break;
        }
    }
    
    if(!contained_by_page) {
        WARNING("GObj does not belong to the given page\n");
        return -1;
    }
    
    list_for_each(_node1, &this_page->gobj_link_root) {
        link_node = container_of(_node1, GOBJ_LINK_NODE, list);
        if(link_node->gobj == gobj) {
            /* Ignore operation if at bottom already */
            if(_node1->prev == &this_page->gobj_link_root)
                return 0;
            list_move(_node1, &this_page->gobj_link_root);
            /* Update overlapped gobj list */
            /* First, traverse the gobj_link of current page reversely */
            list_for_each_reverse(_node2, &this_page->gobj_link_root) {
                link_node2 = container_of(_node2, GOBJ_LINK_NODE, list);
                if(link_node2->gobj == gobj) continue;
                /* Second, traverse the overlapped_gobj_list of each gobj */
                list_for_each_safe(_node3, _node_tmp, &link_node2->gobj->overlapped_gobj_list) {
                    overlapped_node = container_of(_node3, OVERLAPPED_GOBJ_LINK_NODE, list);
                    /* Third, if the to-be-moved-gobj is found in the list of the 
                     * traversing-gobj, then move this OVERLAPPED_GOBJ_LINK_NODE of the 
                     * traversing-gobj to the head of the list of to-be-moved-gobj and
                     * set the gobj pointer of to-be-moved-gobj to traversing-gobj */
                    if(overlapped_node->gobj == gobj) {
                        overlapped_node->gobj = link_node2->gobj;
                        list_move(_node3, &gobj->overlapped_gobj_list);
                    }
                }
            }
            return 0;
        }
    }
    
    return -2; /* GObj is not contained in GObj_link */
}

int PGPushGObjUp(PAGE *this_page, GOBJ *gobj)
{
    struct list_head *_node1, *_node2, *_node3, *_node_tmp, *_node_safe;
    struct list_head *_ov_gobj_list_head;
    BOOLEAN contained_by_page = false;
    PAGE_LIST *pg_lst;
    GOBJ_LINK_NODE *gl_node1, *gl_node2;
    OVERLAPPED_GOBJ_LINK_NODE *ovl_node_next, *ovl_node1, *ovl_node2;
    GOBJ *gobj_next;
    
    list_for_each(_node1, &gobj->pg_list) {
        pg_lst = container_of(_node1, PAGE_LIST, list);
        if(pg_lst->page == this_page) {
            contained_by_page = true;
            break;
        }
    }
    
    if(!contained_by_page) {
        WARNING("GObj does not belong to the given page\n");
        return -1;
    }
    
    list_for_each(_node1, &this_page->gobj_link_root) {
        gl_node1 = container_of(_node1, GOBJ_LINK_NODE, list);
        if(gl_node1->gobj == gobj) {
            /* Ignore the lastest GObj (must be on top) */
            if(_node1 == this_page->gobj_link_root.prev)
                return 0;
            
            /* Adjust overlapped gobj list */
            _ov_gobj_list_head = &gl_node1->gobj->overlapped_gobj_list;
            if(!list_empty(_ov_gobj_list_head)) {
                ovl_node_next = container_of(_ov_gobj_list_head->next, OVERLAPPED_GOBJ_LINK_NODE, list);
                gobj_next = ovl_node_next->gobj;
                list_move(_ov_gobj_list_head->next, &gobj_next->overlapped_gobj_list);
                ovl_node_next->gobj = gobj;

                /* Adjust all overlapped gobj list */
                list_for_each(_node2, &this_page->gobj_link_root) {
                    gl_node2 = container_of(_node2, GOBJ_LINK_NODE, list);
                    if(gl_node2->gobj == gobj || gl_node2->gobj == gobj_next)
                        break;
                    list_for_each_safe(_node3, _node_safe, &gl_node2->gobj->overlapped_gobj_list) {
                        ovl_node1 = container_of(_node3, OVERLAPPED_GOBJ_LINK_NODE, list);
                        ovl_node2 = container_of(_node3->next, OVERLAPPED_GOBJ_LINK_NODE, list);
                        if(ovl_node1->gobj == gobj) {
                            if(ovl_node2->gobj == gobj_next) {
                                _node_tmp = _node3->next;
                                list_move(_node3, _node_tmp);
                            }
                            break;
                        }
                        if(ovl_node1->gobj == gobj_next)
                            break;
                    }
                }
            }
            
            /* Adjust gobj link order */
            _node_tmp = _node1->next;
            list_move(_node1, _node_tmp);
            return 0;
        }
    }
    
    return -2; /* GObj is not contained in GObj_link */
}

int PGPushGObjDown(PAGE *this_page, GOBJ *gobj)
{
    /* Under construction */
    return 0;
}
